/*
 * jxUtils Project (2014)
 * 
 * by Denilson Edinaldo Pinto
 * 
 * LGPL
 * Copyright (C) 2014
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.jxUtils.file;

import java.io.Serializable;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author Denilson Edinaldo Pinto
 * @porject jxUtils
 * @package org.jxUtils.file
 * @date 31/05/2014 - 11:00:20
 */
public final class FileSize implements Serializable, Comparable<FileSize> {
	
	private static final long serialVersionUID = -583517922644269814L;
	
	private static final Map<String, String> SYMBOL_NAME = new LinkedHashMap<>();
	
	private static final DecimalFormat FORMAT = new DecimalFormat();
	
	private static final BigDecimal ONE_BYTE = new BigDecimal(1);
	
	private static final BigDecimal ONE_KB = new BigDecimal(1000);
	
	private static final BigDecimal ONE_KiB = new BigDecimal(1024);
	
	private static final BigDecimal ONE_MB = new BigDecimal(ONE_KB.longValue() * ONE_KB.longValue());
	
	private static final BigDecimal ONE_MiB = new BigDecimal(ONE_KiB.longValue() * ONE_KiB.longValue());
	
	private static final BigDecimal ONE_GB = new BigDecimal(ONE_KB.longValue() * ONE_MB.longValue());
	
	private static final BigDecimal ONE_GiB = new BigDecimal(ONE_KiB.longValue() * ONE_MiB.longValue());
	
	private static final BigDecimal ONE_TB = new BigDecimal(ONE_KB.longValue() * ONE_GB.longValue());
	
	private static final BigDecimal ONE_TiB = new BigDecimal(ONE_KiB.longValue() * ONE_GiB.longValue());
	
	private static final BigDecimal ONE_PB = new BigDecimal(ONE_KB.longValue() * ONE_TB.longValue());
	
	private static final BigDecimal ONE_PiB = new BigDecimal(ONE_KiB.longValue() * ONE_TiB.longValue());
	
	private static final BigDecimal ONE_EB = new BigDecimal(ONE_KB.longValue() * ONE_PB.longValue());
	
	private static final BigDecimal ONE_EiB = new BigDecimal(ONE_KiB.longValue() * ONE_PiB.longValue());
	
	private static final BigDecimal ONE_ZB = ONE_KB.multiply(ONE_EB);
	
	private static final BigDecimal ONE_ZiB = ONE_KiB.multiply(ONE_EiB);
	
	private static final BigDecimal ONE_YB = ONE_KB.multiply(ONE_ZB);
	
	private static final BigDecimal ONE_YiB = ONE_KiB.multiply(ONE_ZiB);
	
	private BigDecimal value = new BigDecimal(0);
	
	private int index = 0;
	
	private TYPE type = TYPE.SI;
	
	static {
		
		SYMBOL_NAME.put("bytes", "Byte");
		SYMBOL_NAME.put("KB", "KiloByte");
		SYMBOL_NAME.put("KiB", "KiloByte");
		SYMBOL_NAME.put("MB", "MegaByte");
		SYMBOL_NAME.put("MiB", "MegaByte");
		SYMBOL_NAME.put("GB", "GigaByte");
		SYMBOL_NAME.put("GiB", "GigaByte");
		SYMBOL_NAME.put("TB", "TeraByte");
		SYMBOL_NAME.put("TiB", "TeraByte");
		SYMBOL_NAME.put("PB", "PetaByte");
		SYMBOL_NAME.put("PiB", "PetaByte");
		SYMBOL_NAME.put("EB", "ExaByte");
		SYMBOL_NAME.put("EiB", "ExaByte");
		SYMBOL_NAME.put("ZB", "ZettaByte");
		SYMBOL_NAME.put("ZiB", "ZettaByte");
		SYMBOL_NAME.put("YB", "YottaByte");
		SYMBOL_NAME.put("YiB", "YottaByte");
	}
	
	/**
	 * @param value
	 */
	FileSize(final int index, final BigDecimal value) {
	
		this.index = index;
		this.value = value;
	}
	
	/**
	 * @param size
	 */
	public FileSize(final long size) {
	
		this(size, TYPE.SI);
	}
	
	/**
	 * @param size
	 * @param type
	 */
	public FileSize(final long size, final TYPE type) {
	
		this.type = type;
		
		final FileSize fileSize = convertSize(size);
		
		index = fileSize.index;
		value = fileSize.getValue();
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@Override
	public int compareTo(final FileSize other) {
	
		int result = 1;
		
		//
		if(other != null) {
			
			//
			if(index == other.index) {
				
				result = getValue().compareTo(other.getValue());
				
			}else if(index < other.index) {
				
				result = -1;
			}
		}
		
		return result;
	}
	
	/**
	 * @param sizeInBytes
	 * @return a human-readable display value (includes units - YiB, YB, ZiB, ZB, EiB, EB, PiB, PB, TiB, TB, GiB, GB, MiB, MB, KiB, KB or bytes)
	 */
	public FileSize convertSize(final BigDecimal sizeInBytes) {
	
		final double BASE = getType() == TYPE.IEC ? ONE_KiB.doubleValue() : ONE_KB.doubleValue();
		
		final double MAX = getType() == TYPE.IEC ? ONE_YiB.doubleValue() : ONE_YB.doubleValue();
		
		final int firstIncrement = getType().equals(TYPE.IEC) ? 2 : 1;
		
		final int precision = 2;
		
		BigDecimal value = sizeInBytes;
		
		int index = 0;
		
		//
		if(value.doubleValue() >= BASE) {
			
			double divisionValue = value.doubleValue() / BASE;
			
			//
			while(index < 7 && divisionValue >= BASE) {
				
				index += index == 0 ? firstIncrement : 2;
				
				value = new BigDecimal(divisionValue);
				
				divisionValue = value.doubleValue() / BASE;
			}
			
			value = new BigDecimal(value.doubleValue() / BASE);
		}
		
		index += index == 0 ? firstIncrement : 2;
		
		FileSize fileSize = null;
		
		//
		if(value.doubleValue() <= MAX) {
			
			fileSize = new FileSize(index, value.setScale(precision, BigDecimal.ROUND_UP));
			
		}else {
			
			fileSize = new FileSize(index, value);
		}
		
		return fileSize;
	}
	
	/**
	 * @param size
	 * @return a human-readable display value (includes units - YiB, YB, ZiB, ZB, EiB, EB, PiB, PB, TiB, TB, GiB, GB, MiB, MB, KiB, KB or bytes)
	 */
	public FileSize convertSize(final double size) {
	
		return convertSize(new BigDecimal(size));
	}
	
	/**
	 * @param size
	 * @return a human-readable display value (includes units - YiB, YB, ZiB, ZB, EiB, EB, PiB, PB, TiB, TB, GiB, GB, MiB, MB, KiB, KB or bytes)
	 */
	public FileSize convertSize(final float size) {
	
		return convertSize(new BigDecimal(size));
	}
	
	/**
	 * @param size
	 * @return a human-readable display value (includes units - YiB, YB, ZiB, ZB, EiB, EB, PiB, PB, TiB, TB, GiB, GB, MiB, MB, KiB, KB or bytes)
	 */
	public FileSize convertSize(final int size) {
	
		return convertSize(new BigDecimal(size));
	}
	
	/**
	 * @param size
	 * @return a human-readable display value (includes units - YiB, YB, ZiB, ZB, EiB, EB, PiB, PB, TiB, TB, GiB, GB, MiB, MB, KiB, KB or bytes)
	 */
	public FileSize convertSize(final long size) {
	
		return convertSize(new BigDecimal(size));
	}
	
	/**
	 * @param size
	 * @return a human-readable display value (includes units - YB, YiB, ZB, ZiB, EB, EiB, PB, PiB, TB, TiB, GB, GiB, MB, MiB, KB, KiB or bytes)
	 */
	public FileSize convertSize(final short size) {
	
		return convertSize(new BigDecimal(size));
	}
	
	/**
	 * @return the initialValue in bytes
	 */
	public BigDecimal getInitialValue() {
	
		switch(index) {
		
			case 0:
				return ONE_BYTE;
			case 1:
				return ONE_KB;
			case 2:
				return ONE_KiB;
			case 3:
				return ONE_MB;
			case 4:
				return ONE_MiB;
			case 5:
				return ONE_GB;
			case 6:
				return ONE_GiB;
			case 7:
				return ONE_TB;
			case 8:
				return ONE_TiB;
			case 9:
				return ONE_PB;
			case 10:
				return ONE_PiB;
			case 11:
				return ONE_EB;
			case 12:
				return ONE_EiB;
			case 13:
				return ONE_ZB;
			case 14:
				return ONE_ZiB;
			case 15:
				return ONE_YB;
			case 16:
				return ONE_YiB;
			default:
				return null;
		}
	}
	
	/**
	 * @return the name
	 */
	public String getName() {
	
		return new ArrayList<>(SYMBOL_NAME.values()).get(index);
	}
	
	/**
	 * @return the symbol
	 */
	public String getSymbol() {
	
		return new ArrayList<>(SYMBOL_NAME.keySet()).get(index);
	}
	
	/**
	 * @return the type
	 */
	public TYPE getType() {
	
		return type;
	}
	
	/**
	 * @return the value
	 */
	public BigDecimal getValue() {
	
		return value;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
	
		return FORMAT.format(getValue()) + " " + getSymbol();
	}
	
	/**
	 * @author Denilson Edinaldo Pinto
	 * @porject jxUtils
	 * @package org.jxUtils.file
	 * @date 31/05/2014 - 11:00:20
	 */
	public static enum TYPE {
		IEC,
		SI
	}
}
